﻿using System;
using System.Configuration;
using System.IdentityModel.Claims;
using System.Web;
using System.Web.Security;
using Altairis.IdentityToolkit.Configuration;

namespace Altairis.IdentityToolkit.LiveId {

    /// <summary>
    /// Class representing HTTP handler processing callback from Windows Live ID service.
    /// </summary>
    public class LiveCallbackHandler : IHttpHandler {

        private static readonly byte[] EmptyGIF = Convert.FromBase64String("R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAEALAAAAAABAAEAAAIBTAA7");
        private LiveConfiguration config;

        /// <summary>
        /// Initializes a new instance of the <see cref="LiveCallbackHandler"/> class.
        /// </summary>
        public LiveCallbackHandler() {
            this.config = ConfigurationManager.GetSection("altairis.identityToolkit/liveId") as LiveConfiguration;
        }

        /// <summary>
        /// Gets a value indicating whether another request can use the <see cref="T:System.Web.IHttpHandler"/> instance.
        /// </summary>
        /// <value></value>
        /// <returns>true if the <see cref="T:System.Web.IHttpHandler"/> instance is reusable; otherwise, false.</returns>
        public bool IsReusable {
            get { return true; }
        }

        /// <summary>
        /// Enables processing of HTTP Web requests by a custom HttpHandler that implements the <see cref="T:System.Web.IHttpHandler"/> interface.
        /// </summary>
        /// <param name="context">An <see cref="T:System.Web.HttpContext"/> object that provides references to the intrinsic server objects (for example, Request, Response, Session, and Server) used to service HTTP requests.</param>
        public void ProcessRequest(HttpContext context) {
            // Perform requested action
            switch (context.Request["action"]) {
                case "login":
                    // Perform login
                    this.Login(context);
                    break;
                case "logout":
                    // Logout and redirect
                    FormsAuthentication.SignOut();
                    context.Response.Redirect(this.config.Handler.LogoutUrl);
                    break;
                case "clearcookie":
                    // Logout only
                    FormsAuthentication.SignOut();
                    context.Response.ContentType = "image/gif";
                    context.Response.OutputStream.Write(EmptyGIF, 0, EmptyGIF.Length);
                    break;
                default:
                    this.WriteErrorMessage(context, "Parameter 'action' is missing, empty or invalid.");
                    break;
            }
        }

        private void Login(HttpContext context) {
            string stoken = context.Request["stoken"];
            if (string.IsNullOrEmpty(stoken)) {
                this.WriteErrorMessage(context, "Required parameter 'stoken' is missing or empty.");
                return;
            }

            // Parse token
            LiveClaimSet claims;
            try {
                claims = new LiveClaimSet(stoken);
            }
            catch (Exception ) {
                context.Response.Redirect(this.config.Handler.FailureUrl);
                return;
            }

            // Check ApplicationId
            if (!claims.ApplicationId.Equals(this.config.Service.ApplicationId)) {
                this.WriteErrorMessage(context, "Received appid does not match with appid of current application.");
                return;
            }

            // Check max age
            if (this.config.Handler.MaxTicketAge != 0) {
                double age = Math.Abs(DateTime.UtcNow.Subtract(claims.Timestamp).TotalMinutes);
                if (age > this.config.Handler.MaxTicketAge) this.SmartRedirect(context, claims, this.config.Handler.TimeoutUrl);
            }

            // Use identity link provider to get mapped user name
            string userName = IdentityLinkManager.ResolveLink(claims);
            if (string.IsNullOrEmpty(userName)) this.SmartRedirect(context, claims, this.config.Handler.UnlinkedUrl);

            // Logon found user using forms authentication
            FormsAuthentication.RedirectFromLoginPage(userName, claims.UsePermanentCookie);
        }

        private void SmartRedirect(HttpContext context, ClaimSet claims, string url) {
            string cacheId = Guid.NewGuid().ToString("N");
            context.Cache.Add(cacheId, claims, null,
                System.Web.Caching.Cache.NoAbsoluteExpiration,
                TimeSpan.FromMinutes(this.config.Handler.CacheTimeout),
                System.Web.Caching.CacheItemPriority.NotRemovable,
                null);
            if (url.IndexOf('?') == -1) url += "?CSID=" + cacheId;
            else url += "&CSID=" + cacheId;
            context.Response.Redirect(url);
        }

        private void WriteErrorMessage(HttpContext context, string message) {
            context.Response.StatusCode = 400;
            context.Response.StatusDescription = "Bad Request";
            context.Response.Output.WriteLine("<html>");
            context.Response.Output.WriteLine("<head><title>400 Bad Request</title></head>");
            context.Response.Output.WriteLine("<body>");
            context.Response.Output.WriteLine("<h1>400 Bad Request</h1>");
            context.Response.Output.WriteLine("<p>" + message + "</p>");
            context.Response.Output.WriteLine("</body></html>");
            context.ApplicationInstance.CompleteRequest();
        }

    }
}
